// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <cstring>
#include <time.h>
#include <chrono>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include <sys/ioctl.h>

#include "CommonMaster.h"
#include "SocketServer.h"
#include "securec.h"

namespace master {
std::map<uint32_t, std::shared_ptr<SocketInfoT>> recvInfoMap;

SocketServer::SocketServer()
{
}

SocketServer::~SocketServer()
{
}

int SocketServer::acceptNewConnect(int *fd)
{
    int connFd = 0;
    char ipAddr[IP_ADDR_LENGTH] = {0};
    uint16_t port;

    connFd = this->socketAccept(ipAddr, &port);
    if (connFd < 0) {
        LOG_ERROR_FMT("Server accept connect fd %d failed", connFd);
        return ENC_FAIL;
    }
    *fd = connFd;

    auto pSocketInfo = this->createSocketInfoIt(connFd);
    if (nullptr != pSocketInfo) {
        pSocketInfo->fdSock = connFd;
    } else {
        LOG_ERROR_FMT("This sock fd exist, please check client connect two times");
        socketClose(connFd);
    }

    pSocketInfo->ipAddr = ipAddr;
    pSocketInfo->port = port;

    LOG_INFO_FMT("New connect ipAddr=%s port=%d", pSocketInfo->ipAddr.c_str(), pSocketInfo->port);
    return ENC_OK;
}

std::shared_ptr<SocketInfoT> SocketServer::findSocketInfoIt(int connFd)
{
    std::lock_guard<std::mutex> locker(mutexMap);
    std::shared_ptr<SocketInfoT> pSocketInfo = nullptr;
    std::map<uint32_t, std::shared_ptr<SocketInfoT>>::iterator socketInfoIt;

    socketInfoIt = recvInfoMap.find(connFd);
    if (socketInfoIt != recvInfoMap.end()) {
        if (nullptr != socketInfoIt->second) {
            pSocketInfo = socketInfoIt->second;
        }
    }

    return pSocketInfo;
}

int SocketServer::eraseSocketInfoIt(int connFd)
{
    auto pSocketInfo = this->findSocketInfoIt(connFd);
    if (nullptr == pSocketInfo) {
        return ENC_FAIL;
    }

    std::lock_guard<std::mutex> locker(mutexMap);
    recvInfoMap.erase(connFd);
    this->socketClose(connFd);

    return ENC_OK;
}

std::shared_ptr<SocketInfoT> SocketServer::createSocketInfoIt(int connFd)
{
    std::lock_guard<std::mutex> locker(mutexMap);
    std::shared_ptr<SocketInfoT> pSocketInfo;
    std::map<uint32_t, std::shared_ptr<SocketInfoT>>::iterator socketInfoIt;
    socketInfoIt = recvInfoMap.find(connFd);
    if (socketInfoIt != recvInfoMap.end()) {
        LOG_ERROR_FMT("Create socket info failed");
        return socketInfoIt->second;
    }
    pSocketInfo = std::make_shared<SocketInfoT>();

    auto pRecv = std::shared_ptr<uint8_t>(new uint8_t[SRC_BUF_LEN], std::default_delete<uint8_t[]>());
    if (nullptr == pRecv) {
        LOG_ERROR_FMT("New src buf failed");
        return nullptr;
    }
    pSocketInfo->recvBuf = pRecv;

    auto pSend = std::shared_ptr<uint8_t>(new uint8_t[DST_BUF_LEN], std::default_delete<uint8_t[]>());
    if (nullptr == pSend) {
        LOG_ERROR_FMT("New dst buf failed");
        return nullptr;
    }
    pSocketInfo->sendBuf = pSend;

    pSocketInfo->recvBufLen = 0;
    pSocketInfo->recvBufOffset = 0;
    pSocketInfo->sendBufLen = 0;
    pSocketInfo->firstRun = true;
    recvInfoMap.insert(std::pair<uint32_t, std::shared_ptr<SocketInfoT>>(connFd, pSocketInfo));

    return pSocketInfo;
}

int SocketServer::socketServerInit(uint16_t ipPort)
{
    this->socketSetParamInfo(ipPort);

    if (ENC_OK != this->socketSetFd()) {
        LOG_ERROR_FMT("Socket set fd failed");
        return ENC_FAIL;
    }

    if (ENC_OK != this->socketSetSockOpt()) {
        LOG_ERROR_FMT("Socket set opt failed");
        return ENC_FAIL;
    }

    if (ENC_OK != this->socketBind()) {
        LOG_ERROR_FMT("Socket bind failed");
        return ENC_FAIL;
    }

    if (ENC_OK != this->socketListen()) {
        LOG_ERROR_FMT("Socket listen failed");
        return ENC_FAIL;
    }

    return ENC_OK;
}

int SocketServer::socketServerReadData(std::shared_ptr<SocketInfoT> pSocketInfo)
{
    int ret = ENC_OK;
    int recvLen = 0;
    pSocketInfo->recvBufLen = SOCKET_HEAD_LENGTH;
    pSocketInfo->recvBufOffset = 0;
    this->eagainCnt = 0;

    while (true) {
        ret = this->socketRecv(pSocketInfo->fdSock, pSocketInfo->recvBuf.get() + pSocketInfo->recvBufOffset,
                                   pSocketInfo->recvBufLen - pSocketInfo->recvBufOffset, &recvLen);
        if (ret < 0) {
            return ENC_FAIL;
        }
        pSocketInfo->recvBufOffset += recvLen;
        if (pSocketInfo->recvBufOffset == SOCKET_HEAD_LENGTH) {
            ret = memcpy_s(&pSocketInfo->recvBufLen, SOCKET_DATA_BUF_LENGTH,
                           pSocketInfo->recvBuf.get() + SOCKET_HEAD_LENGTH - SOCKET_DATA_BUF_LENGTH,
                           SOCKET_DATA_BUF_LENGTH);
            if ((ret < 0) || (pSocketInfo->recvBufLen <= 0)) {
                LOG_ERROR_FMT("Socket recv buf len less than zero");
                return ENC_FAIL;
            }
            pSocketInfo->recvBufLen += pSocketInfo->recvBufOffset;
        }
        if (pSocketInfo->recvBufOffset == pSocketInfo->recvBufLen) {
            pSocketInfo->recvBufOffset = 0;
            break;
        }
    }
    return ENC_OK;
}

int SocketServer::socketServerWriteData(int connFd, std::shared_ptr<SocketInfoT> pSocketInfo)
{
    int ret = ENC_OK;
    uint32_t recvLen = 0;
    int sendBufOffset = 0;

    while (sendBufOffset < pSocketInfo->sendBufLen) {
        ret = this->socketSend(connFd, pSocketInfo->sendBuf.get() + sendBufOffset,
                               pSocketInfo->sendBufLen - sendBufOffset, &recvLen);
        if (ENC_OK != ret) {
            LOG_ERROR_FMT("Socket send failed");
            return ENC_FAIL;
        }
        sendBufOffset += recvLen;
    }
    return ENC_OK;
}
};
